#include "scull.h"
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

unsigned int scull_major = SCULL_MAJOR;
unsigned int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS;
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;

module_param(scull_major, unsigned int, S_IRUGO);
module_param(scull_minor, unsigned int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);

MODULE_AUTHOR("Zen Lian");
MODULE_LICENSE("Dual BSD/GPL");

struct scull_dev *scull_devices;

// TODO: 截断为 0
static int scull_trim(struct scull_dev *dev)
{

}

struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    //.llseek = scull_llseek,
    .read = scull_read,
    .write = scull_write,
    //.ioctl = scull_ioctl,
    .open = scull_open,
    .release = scull_release,
};

/* open */
int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev;
    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev;

    // 当设备为写而打开时，将长度截断为0
    if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        scull_trim(dev); /* ignore errors */
        up(&dev->sem);
    }

    return 0;
}

/* release */
int scull_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* TODO: read */
ssize_t scull_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{

}

/* TODO: write */
ssize_t scull_write(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{

}

// TODO
static void scull_exit(void)
{

}

static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    int err, devno = MKDEV(scull_major, scull_minor + index);

    cdev_init(&dev->cdev, &scull_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &scull_fops;
    err = cdev_add(&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

static int __init scull_init(void)
{
    int result, i;
    dev_t dev = 0;

    // 分配设备号
    if (scull_major) {
        dev = MKDEV(scull_major, scull_minor);
        result = resregister_chrdev_region(dev, scull_nr_devs, "scull");
    } else {
        result = alloc_chrdev_region(&dev, scull_major, scull_nr_devs, "scull");
        scull_major = MAJOR(dev);
    }

    if (result < 0) {
        printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
        return result;
    }

    // 分配 scull_dev 空间
    scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
    if (!scull_devices) {
        result = -ENOMEM;
        goto fail;
    }
    memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

    // 初始化每个设备
    for (i = 0; i < scull_nr_devs; ++i) {
        scull_devices[i].data = NULL;
        scull_devices[i].quantum = scull_quantum;
        scull_devices[i].qset = scull_qset;
        scull_devices[i].size = 0;
        sema_init(&scull_devices[i].sem, 1);
        scull_setup_cdev(scull_devices + i, i);
    }

    // TODO: 其他设备

fail:
    scull_exit();
    return result;
}


module_init(scull_init);
module_exit(scull_exit);
